package ru.compft.app.as_db.business;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import ru.compft.common.data.AppPermission;
import ru.compft.common.data.AppRole;
import ru.compft.common.data.AppUser;
import ru.compft.common.exception.PermissionDeniedException;
import ru.compft.common.services.PermissionsService;
import ru.compft.common.utils.GlobalConstants;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;

/**
 * User: ASUS
 * Date: 28.10.12
 * Time: 17:09
 * Выполняет авторизацию пользователя - проверка полномочий на выполнение методов
 * Получаем текущего пользователя из SecurityContext
 */
@Aspect
@Component
public class BusinessFacadeInterceptor {
    private static final Logger logger = LoggerFactory.getLogger(BusinessFacadeInterceptor.class);

    @Resource(name = "permissionsService")
    private PermissionsService permissionsService;

    public BusinessFacadeInterceptor() {
        System.out.println("******* BusinessFacadeInterceptor constructor *******");
        System.out.println("* Autowired permissionsService {" + permissionsService + "} *");
    }

    @Pointcut("this(ru.compft.app.as_db.business.BusinessFacade)")
    public void businessOperation() {
    }

    @Before("businessOperation()")
    public void checkPermissions(JoinPoint joinPoint) throws Throwable {
        // Объект, который выполняем метод
        // должен расширять интерфейс BusinessFacade
        final Object target = joinPoint.getTarget();

        // получаем имя класса и метода перехваченного объекта
        final StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append(target.getClass().getName());
        stringBuilder.append(".");
        stringBuilder.append(joinPoint.getSignature().getName());

        // Это ключ для таблицы Permissions
        final String key = stringBuilder.toString();

        if (logger.isDebugEnabled()) {
            logger.debug("Business method full name = " + key);
        }
        System.out.println("Business method full name = " + key);

        // Из таблицы Permissions получаем запись для данного метода по коду
        final AppPermission appPermission = permissionsService.getPermissionByCode(key);

        if (logger.isDebugEnabled()) {
            logger.debug("Permission for business method {" + key + "} = " + appPermission);
        }
        System.out.println("Permission for business method {" + key + "} = " + appPermission);

        // если такая запись существует, то продолжаем проверку прав доступа
        // если нет, то значит на метод нет ограничений и мы просто выполняем метод
        if (appPermission != null) {
            // Если на метод определены прва доступа, значит он сможет выполнится только при наличии этих прав
            // Проверяем доступен ли данный метод данному пользователю
            // т.е. есть ли права у пользователя на данный метод.
            // получаем аутентифицированного пользователя из контекста
            final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

            boolean hasPermission = false; // флаг, что найдена роль для данного метода

            // Если пользователь аутентифицирован
            if (authentication != null && authentication.isAuthenticated()) {
                // получим все доступные роли (коды ролей) для данного пользователя
                final List<String> appUserRoleList = getRolesCodeList(authentication.getAuthorities());
                // получим все роли (коды ролей), которым разрешено выполнять данный метод.
                final List<String> permissionRoleList = getAppRolesCodeList(appPermission.getRoles());

                // если оба списка не пустые
                if (!CollectionUtils.isEmpty(appUserRoleList) && !CollectionUtils.isEmpty(permissionRoleList)) {
                    if (logger.isDebugEnabled()) {
                        logger.debug("User has {" + appUserRoleList + "} roles");
                    }
                    System.out.println("User has {" + appUserRoleList + "} roles");

                    if (logger.isDebugEnabled()) {
                        logger.debug("Method enabled to {" + permissionRoleList + "} roles");
                    }
                    System.out.println("Method enabled to {" + permissionRoleList + "} roles");

                    // перебираем все роли
                    for (String appRole : appUserRoleList) {
                        if (permissionRoleList.contains(appRole)) {
                            if (logger.isDebugEnabled()) {
                                logger.debug("User with role {" + appRole + "} can execute this method {" + key + "}");
                            }
                            System.out.println("User with role {" + appRole + "} can execute this method {" + key + "}");

                            hasPermission = true;
                            break;
                        }
                    }
                }
            }

            // если роль так и не нашли, бросаем исключение PermissionDeniedException
            if (!hasPermission) userHasNoPrivilegies(null, key);
        }
    }

    /**
     * Заполняем список кодов ролей
     *
     * @param roles
     * @return
     */
    private List<String> getAppRolesCodeList(Set<AppRole> roles) {
        final List<String> resultList = new ArrayList<String>();
        if (!CollectionUtils.isEmpty(roles)) {
            for (AppRole role : roles) {
                resultList.add(role.getCode());
            }
        }
        return resultList;
    }


    /**
     * Заполняем список кодов ролей
     *
     * @param roles
     * @return
     */
    private List<String> getRolesCodeList(Collection<? extends GrantedAuthority> roles) {
        final List<String> resultList = new ArrayList<String>();
        if (!CollectionUtils.isEmpty(roles)) {
            for (GrantedAuthority authority : roles) {
                resultList.add(authority.getAuthority());
            }
        }
        return resultList;
    }

    /**
     * Если у пользователя нет прав - бросам исключение
     *
     * @param user
     * @param methodKey
     * @throws PermissionDeniedException
     */
    private void userHasNoPrivilegies(AppUser user, String methodKey) throws PermissionDeniedException {
        final String login = user != null ? user.getLogin() : GlobalConstants.ANONYMOUS;
        if (logger.isWarnEnabled()) {
            logger.warn("User {" + login + "} has no permissions to method {" + methodKey +
                    "}. Will throw new PermissionDeniedException");
        }
        System.out.println("User {" + login + "} has no permissions to method {" + methodKey +
                "}. Will throw new PermissionDeniedException");

        // если роль не была найдена, то бросаем исключение о нарушении прав доступа
        throw new PermissionDeniedException("User " + login + " has no permissions to method " + methodKey +
                ". Check your DB or ask administrator.");
    }
}
